{
 "cells": [
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:14.244246Z",
     "start_time": "2025-02-05T00:57:11.270746Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 导入库\n",
    "import matplotlib as mpl\n",
    "# Matplotlib 绘制的图形会直接嵌入到 Notebook 的输出单元格中，而不是弹出一个独立的窗口\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "\n",
    "# os库提供了一种使用操作系统相关功能的便捷方式，允许与操作系统进行交互\n",
    "import os\n",
    "\n",
    "# sys库主要用于处理Python运行时的环境相关信息以及与解释器的交互\n",
    "import sys\n",
    "import time\n",
    "\n",
    "# tqdm库是一个快速，可扩展的Python进度条，可以在 Python 长循环中添加一个进度提示信息，用户只需要封装任意的迭代器 tqdm(iterator)，即可获得一个进度条显示迭代进度。\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "\n",
    "# torch.nn是PyTorch中用于构建神经网络的模块\n",
    "import torch.nn as nn\n",
    "\n",
    "# torch.nn.functional是PyTorch中包含了神经网络的一些常用函数\n",
    "import torch.nn.functional as F"
   ],
   "id": "4c1630b7ca07d2af",
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:14.248329Z",
     "start_time": "2025-02-05T00:57:14.245251Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import torch\n",
    "\n",
    "print(torch.cuda.is_available())  # 如果返回 False，说明 CUDA 不可用\n",
    "\n",
    "print(torch.version.cuda)"
   ],
   "id": "d448e9b8c50d3c83",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "None\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:14.252658Z",
     "start_time": "2025-02-05T00:57:14.248329Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# Python 中的一个属性，用于获取当前 Python 解释器的版本信息。它返回一个命名元组（named tuple）\n",
    "print(sys.version_info)\n",
    "\n",
    "# 遍历模块，打印模块名称和版本信息，快速检查当前环境中安装的某些常用 Python 库的版本信息\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "# 判断是否有 GPU，如果有，则使用 GPU 进行训练，否则使用 CPU 进行训练\n",
    "# torch.cuda.is_available()用于判断是否有GPU\n",
    "# torch.device(\"cuda:0\")创建一个 PyTorch 设备对象，表示使用第一个 GPU（索引为 0）\n",
    "# torch.device(\"cpu\")创建一个 PyTorch 设备对象，表示使用 CPU\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)"
   ],
   "id": "ee3dc34e02de7c22",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 数据准备1，为了看灰度图片,不对图片做任何处理",
   "id": "99875b9649c85490"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.401324Z",
     "start_time": "2025-02-05T00:57:14.252658Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 提供了许多常用的公开数据集\n",
    "from torchvision import datasets\n",
    "\n",
    "# 导入 ToTensor 类，它是一个图像变换工具，用于将 PIL 图像或 NumPy 数组转换为 PyTorch 张量（torch.Tensor）\n",
    "# 同时将像素值从 [0, 255] 缩放到 [0.0, 1.0]\n",
    "# 在加载数据集时，通常需要将图像转换为张量，以便输入到神经网络中\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# 图像预处理工具，用于对图像进行各种变换（如缩放、裁剪、旋转、归一化等）\n",
    "from torchvision import transforms\n",
    "\n",
    "# PyTorch 中用于定义图像预处理流水线的方式\n",
    "# transforms.Compose 将多个图像变换操作组合在一起，按顺序依次应用到图像上\n",
    "# 传入一个空列表 []，则表示不进行任何变换,定义一个空的变换流水线\n",
    "transform = transforms.Compose([])\n",
    "\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载训练集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载测试集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 可以用 torch.utils.data.Dataset 实现人为划分"
   ],
   "id": "706f6a54efffccb0",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.412001Z",
     "start_time": "2025-02-05T00:57:15.402331Z"
    }
   },
   "cell_type": "code",
   "source": [
    "print(\"训练集的类型：\\n\", type(train_ds))\n",
    "print(\"测试集的大小：\\n\", len(test_ds))\n",
    "\n",
    "# 通过id取数据，取到的是一个元祖,是第一个样本,在训练时，把特征和标签分开\n",
    "img, label = train_ds[0]\n",
    "# 显示图片，如果 img 是张量，会显示张量的值和形状。\n",
    "print(\"数据集的第一张图片：\\n\", img)\n",
    "print(\"数据集的第一张图片的类型：\\n\", type(img))\n",
    "print(\"数据集的第一张图片的标签：\\n\", label)"
   ],
   "id": "e6b70263c0f3ee4d",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集的类型：\n",
      " <class 'torchvision.datasets.mnist.FashionMNIST'>\n",
      "测试集的大小：\n",
      " 60000\n",
      "数据集的第一张图片：\n",
      " <PIL.Image.Image image mode=L size=28x28 at 0x1FD52471D60>\n",
      "数据集的第一张图片的类型：\n",
      " <class 'PIL.Image.Image'>\n",
      "数据集的第一张图片的标签：\n",
      " 9\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.426770Z",
     "start_time": "2025-02-05T00:57:15.412001Z"
    }
   },
   "cell_type": "code",
   "source": "img",
   "id": "9c94c931819d32f1",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<PIL.Image.Image image mode=L size=28x28>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAACS0lEQVR4AWKgA2BkYOD1ZGBgZAHZxcjIAKZBbBBm+quS8v3rj1N/GBiZGP8wMKNIMv91cnnCzuU65+X/vww8/76hSP5iMFVgZtpp2HXm8nUz02PHGUHGQTHjf9cugd//GE7f+cUo8ft0yDSEJCMDw/8TCgyMf34x/Ph3/vYfT0VphLH/GRgY3kt+Z2fl+cH5z8aSSWwHqmsZuJiZvn18p/CPkYnr7z9ZBiaofQwMjMwMPFI/frH++sr/j537K9sldhOE5H9mhnBJJg4Gbtlf7L//cQhvusaCkGT5xXDlBxsXl6rSD2Yunr9PoraeYAGZx8T4+x/DHwaGbV+/s/1/zczxm+H3P2a9jwxMDMz///z6+Y+BwW7ime9v//z78/XrXw6GbwxsX4NAYc3AICSlJhmk/oPpN+czVjbhX1zHeOz+fWR9qcnIYNkkKvCX+cMfrl+M36+HneEVVGC4x/v5GycPHxcj83GpP3+/MTB/Z2DgF0lwy3z24/49VeFfrLxsf+UBY0xqv8vDw87Ayv/4mSiTRACHIrexMdMvJjYGRlYLlpeP+X485mHje/eQ5/uPP+svKwj9+vD77y/Wf4xsaixP/z/mFvnw5jULOysHL9Mbza+P37O/+f3nN6fERwOWC+sTn937wcPGwcb88+//by/+/WX5wfPrw4fffxRfMjIweBWLv/7wl5mNhZnxPysrGysjA+NLBrZ/EpfCGJn+MTA4tYnxMzGz/GV8+f/pvy/MDP9/f2Paff0YJBAYGBg0RN/LPPx1Fx5HFDIAaCTYdiCc4RIAAAAASUVORK5CYII=",
      "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/wAALCAAcABwBAREA/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/9oACAEBAAA/APn+tbw1oNx4m8QWmkWx2yXD4LkZCADJJ+gFbviL4a63oc7COE3MW4hdn38duD976jNc9daDqllIsc9lKrMu4YGeMkdR7gj8KzcV7H8BtEvV16+1iWCeG1Wz8mOV02pIzupwCeuAp6Z98cZ90aIzLIlw0c0ZJ4KgjHoeOa+evjS9n/wnMcNxBPCYLKONFhA2FNzMpGenDcgd816V4K03wefC+m3NlpVhP+5QSXBiR5fMx825iMg5zwce3FdbOzTwgW90lu6uCm8eYrL02soIyCPQgggEdMGQ3cluiPNK0rJwrRQBNueuMkt+teNfGKxsdY8WWdxNqcNo66eieXMwVsb5DnH415Hp2rajpE5n02/urOUjBe3laMkehIPIrVm8eeLrhNknibVivoLtx/I1UPinxC3XXtUP1vJP8ay5JZJpGkldnduSzHJP41//2Q=="
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.431290Z",
     "start_time": "2025-02-05T00:57:15.427286Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 显示图像的基本信息（如大小、模式）以及像素值（如果是灰度图）\n",
    "# 直接传入 PyTorch 张量（img），代码会报错\n",
    "def show_img_content(img):\n",
    "    from PIL import Image\n",
    "\n",
    "    # 打开一个图像文件\n",
    "    #img = Image.open(img)\n",
    "    print(\"图像大小:\", img.size)\n",
    "    print(\"图像模式:\", img.mode)\n",
    "\n",
    "    # 如果图像是单通道的，比如灰度图，获取像素值列表：\n",
    "    # 图像模式是 'L'，则表示它是灰度图\n",
    "    if img.mode == 'L':\n",
    "        # 获取图像的像素值列表。\n",
    "        pixel_values = list(img.getdata())\n",
    "        print(pixel_values)\n",
    "\n",
    "\n",
    "show_img_content(img)"
   ],
   "id": "29b2d3561bb27e0b",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "图像大小: (28, 28)\n",
      "图像模式: L\n",
      "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 13, 73, 0, 0, 1, 4, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 36, 136, 127, 62, 54, 0, 0, 0, 1, 3, 4, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 102, 204, 176, 134, 144, 123, 23, 0, 0, 0, 0, 12, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155, 236, 207, 178, 107, 156, 161, 109, 64, 23, 77, 130, 72, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 69, 207, 223, 218, 216, 216, 163, 127, 121, 122, 146, 141, 88, 172, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 200, 232, 232, 233, 229, 223, 223, 215, 213, 164, 127, 123, 196, 229, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 183, 225, 216, 223, 228, 235, 227, 224, 222, 224, 221, 223, 245, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 228, 218, 213, 198, 180, 212, 210, 211, 213, 223, 220, 243, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 12, 219, 220, 212, 218, 192, 169, 227, 208, 218, 224, 212, 226, 197, 209, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 99, 244, 222, 220, 218, 203, 198, 221, 215, 213, 222, 220, 245, 119, 167, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 55, 236, 228, 230, 228, 240, 232, 213, 218, 223, 234, 217, 217, 209, 92, 0, 0, 0, 1, 4, 6, 7, 2, 0, 0, 0, 0, 0, 237, 226, 217, 223, 222, 219, 222, 221, 216, 223, 229, 215, 218, 255, 77, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 62, 145, 204, 228, 207, 213, 221, 218, 208, 211, 218, 224, 223, 219, 215, 224, 244, 159, 0, 0, 0, 0, 0, 18, 44, 82, 107, 189, 228, 220, 222, 217, 226, 200, 205, 211, 230, 224, 234, 176, 188, 250, 248, 233, 238, 215, 0, 0, 57, 187, 208, 224, 221, 224, 208, 204, 214, 208, 209, 200, 159, 245, 193, 206, 223, 255, 255, 221, 234, 221, 211, 220, 232, 246, 0, 3, 202, 228, 224, 221, 211, 211, 214, 205, 205, 205, 220, 240, 80, 150, 255, 229, 221, 188, 154, 191, 210, 204, 209, 222, 228, 225, 0, 98, 233, 198, 210, 222, 229, 229, 234, 249, 220, 194, 215, 217, 241, 65, 73, 106, 117, 168, 219, 221, 215, 217, 223, 223, 224, 229, 29, 75, 204, 212, 204, 193, 205, 211, 225, 216, 185, 197, 206, 198, 213, 240, 195, 227, 245, 239, 223, 218, 212, 209, 222, 220, 221, 230, 67, 48, 203, 183, 194, 213, 197, 185, 190, 194, 192, 202, 214, 219, 221, 220, 236, 225, 216, 199, 206, 186, 181, 177, 172, 181, 205, 206, 115, 0, 122, 219, 193, 179, 171, 183, 196, 204, 210, 213, 207, 211, 210, 200, 196, 194, 191, 195, 191, 198, 192, 176, 156, 167, 177, 210, 92, 0, 0, 74, 189, 212, 191, 175, 172, 175, 181, 185, 188, 189, 188, 193, 198, 204, 209, 210, 210, 211, 188, 188, 194, 192, 216, 170, 0, 2, 0, 0, 0, 66, 200, 222, 237, 239, 242, 246, 243, 244, 221, 220, 193, 191, 179, 182, 182, 181, 176, 166, 168, 99, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 61, 44, 72, 41, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.508635Z",
     "start_time": "2025-02-05T00:57:15.431798Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 用于显示单张图像，并添加颜色条（如果是灰度图）\n",
    "# 直接传入 PyTorch 张量（img），代码会报错\n",
    "\n",
    "#img_arr 图像数据，可以是 NumPy 数组、PIL 图像或类似数组的对象\n",
    "def show_single_image(img_arr):\n",
    "    # cmap=\"binary\"：指定颜色映射，\"binary\" 表示灰度图\n",
    "    plt.imshow(img_arr, cmap=\"binary\")\n",
    "\n",
    "    #在图像旁边显示颜色条，表示像素值的范围\n",
    "    plt.colorbar()\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "show_single_image(img)"
   ],
   "id": "2070205e83f6dd9d",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据准备2，为训练做准备，将图片转换为张量，并计算均值和方差",
   "id": "cb9b579fb3df00e2"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.562702Z",
     "start_time": "2025-02-05T00:57:15.508635Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 提供了许多常用的公开数据集\n",
    "from torchvision import datasets\n",
    "\n",
    "# 导入 ToTensor 类，它是一个图像变换工具，用于将 PIL 图像或 NumPy 数组转换为 PyTorch 张量（torch.Tensor）\n",
    "# 同时将像素值从 [0, 255] 缩放到 [0.0, 1.0]\n",
    "# 在加载数据集时，通常需要将图像转换为张量，以便输入到神经网络中\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# 图像预处理工具，用于对图像进行各种变换（如缩放、裁剪、旋转、归一化等）\n",
    "from torchvision import transforms\n",
    "\n",
    "# PyTorch 中用于定义图像预处理流水线的方式\n",
    "# transforms.Compose 将多个图像变换操作组合在一起，按顺序依次应用到图像上\n",
    "\n",
    "# transforms.ToTensor()作用：将 PIL 图像或 NumPy 数组转换为 PyTorch 张量，同时进行以下操作：\n",
    "# 将像素值从 [0, 255] 缩放到 [0.0, 1.0]。\n",
    "# 调整图像数据的维度顺序，从 (H, W, C)（高度、宽度、通道）转换为 (C, H, W)（通道、高度、宽度）。\n",
    "# 输入：PIL 图像或 NumPy 数组。\n",
    "# 输出：PyTorch 张量，形状为 (C, H, W)。\n",
    "transform = transforms.Compose(\n",
    "    [transforms.ToTensor(),  # 将图像转换为张量,并进行归一化\n",
    "     # transforms.Normalize(mean, std),# 这里的mean和std是计算得到的，分别是训练集的均值和方差\n",
    "     ])\n",
    "\n",
    "# fashion_mnist图像分类数据集，衣服分类，60000张训练图片，10000张测试图片\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载训练集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",  # 数据集存储路径\n",
    "    train=True,  # 加载测试集\n",
    "    download=True,  # 如果数据集不存在，则自动下载\n",
    "    transform=transform  # 对图像应用的变换\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 可以用 torch.utils.data.Dataset 实现人为划分"
   ],
   "id": "ece4abe886623014",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:15.572327Z",
     "start_time": "2025-02-05T00:57:15.563706Z"
    }
   },
   "cell_type": "code",
   "source": [
    "img, label = train_ds[0]\n",
    "print(\"数据集的第一张图片的标签：\\n\", label)\n",
    "print(\"数据集的第一张图片的类型：\\n\", type(img))\n",
    "\n",
    "# 对于 PyTorch 张量，应该使用 img.shape 而不是 img.size\n",
    "print(\"数据集的第一张图片的形状：\\n\", img.shape)"
   ],
   "id": "49f4669ce03e0ff7",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集的第一张图片的标签：\n",
      " 9\n",
      "数据集的第一张图片的类型：\n",
      " <class 'torch.Tensor'>\n",
      "数据集的第一张图片的形状：\n",
      " torch.Size([1, 28, 28])\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.178813Z",
     "start_time": "2025-02-05T00:57:15.572327Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 计算数据集中所有图像的均值和标准差\n",
    "# ds：数据集对象，通常是一个可迭代对象，返回元组 (img, label)\n",
    "def cal_mean_std(ds):\n",
    "    # 初始化均值和标准差的累加器\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "\n",
    "    # 对每个图像的每个通道计算均值，然后对所有图像求平均\n",
    "\n",
    "    # 遍历每张图片,_ 是标签（未使用）\n",
    "    for img, _ in ds:\n",
    "        # 计算每张图片的均值和方差,dim=(1, 2) 表示在高度和宽度维度上计算均值。\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "\n",
    "    # 将累加的均值和标准差除以数据集大小，得到整个数据集的均值和标准差\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))"
   ],
   "id": "5d1fc3e2023a9555",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2860]), tensor([0.3205]))\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.382587Z",
     "start_time": "2025-02-05T00:57:18.179817Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 显示数据集中指定数量的图像\n",
    "# n_rows：行数,n_cols：列数,trains_ds：数据集对象,class_names：类别名称列表\n",
    "def show_imgs(n_rows, n_cols, trains_ds, class_names):\n",
    "    # 确保打印的图片小于总样本数，防止索引越界\n",
    "    assert n_rows * n_cols < len(train_ds)\n",
    "\n",
    "    # 要显示的图片太多，需要放大显示\n",
    "    plt.figure(figsize=(n_cols * 1.4, n_rows * 1.6))\n",
    "    for row in range(n_rows):\n",
    "        for col in range(n_cols):\n",
    "            # 计算当前图像的索引\n",
    "            index = n_cols * row + col\n",
    "\n",
    "            # 使用 plt.subplot 创建子图\n",
    "            plt.subplot(n_rows, n_cols, index + 1)\n",
    "\n",
    "            # 从数据集中获取图像和标签\n",
    "            img_arr, label = trains_ds[index]\n",
    "\n",
    "            # 通道换到最后一维，因为imshow需要(H,W,C)，而tensor是(C,H,W)\n",
    "            # 调整图像张量的维度顺序，从 (C, H, W) 转换为 (H, W, C)\n",
    "            img_arr = np.transpose(img_arr, (1, 2, 0))\n",
    "\n",
    "            plt.imshow(img_arr, cmap=\"binary\", interpolation='nearest')\n",
    "\n",
    "            # 去除坐标系\n",
    "            plt.axis('off')\n",
    "\n",
    "            # 显示类别名称\n",
    "            plt.title(class_names[label])\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress',\n",
    "               'Coat', 'Sandal', 'Shirt', 'Sneaker',\n",
    "               'Bag', 'Ankle boot']\n",
    "print(\"训练集的前15张图片：\\n\")\n",
    "# 显示训练集中的前 15 张图像（3 行 5 列)\n",
    "show_imgs(3, 5, train_ds, class_names)"
   ],
   "id": "c528bcc99d4e58da",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集的前15张图片：\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 700x480 with 15 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGMCAYAAADA5EjBAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAb4tJREFUeJztnQe41FS3hoPSe++9SRekIwqCiDQFRRFFQBRRUcGG/Cr2hogoImDFgvyi0hEFFJCuFKnSm/TeBFGQuc/KvZP7ZZ3sMOdwymTme5/nwJ7Jnkxmt+ysmi4UCoUsQgghhJAAcklaXwAhhBBCSFLhRoYQQgghgYUbGUIIIYQEFm5kCCGEEBJYuJEhhBBCSGDhRoYQQgghgYUbGUIIIYQEFm5kCCGEEBJYuJEhhBBCSGCJqo1M9+7drezZs1+wXtOmTe2/5ELOVa1atWQ7H0ketm/fbqVLl8568803L1j3+eeft+uS6IH9R0jasj1O5uBFb2SGDx9u//j69esnzxXFGa+++qo1ceJEK4hIv0fyN2fOHCuaOH36tD1po+26Uhv2X+zx6aefuvouc+bMVtGiRa2WLVtaQ4cOtU6ePJnWl0gAzsHkIf3FnuDLL7+0Spcubf3666/W5s2brfLlyyfPlcXRRqZjx45W+/btraDxxRdfuF5//vnn1syZMxO8X7ly5RS/lmeeecbq379/xJPwhRdesMvJKdkLGuy/2OXFF1+0ypQpY509e9bat2+ffcPp27ev9dZbb1mTJ0+2atSokdaXSDgHo2Mjs23bNmvhwoXW+PHjrV69etmbmueeey75ro5ENV26dHG9Xrx4sT0J9fupQfr06e0/P86fP2/9888/qXZN0Q77L3Zp1aqVVadOHef1f/7zH2vWrFlW27ZtrRtuuMFat26dlSVLFs/Pnjp1ysqWLVsqXm38wjkYBaol2bjkyZPHatOmjS1VkNd+OroPPvjAKleunJUpUyarbt261pIlSy74HStWrLAKFChg7/r+/PNPY72///7b3kSJREjOX6JECatfv372+5GybNkyq1GjRvYEl6eZkSNHJqhz4MAB6+6777YKFSpki20vv/xy67PPPvNcDB577DH7OuR6LrvsMrsNMNm4tIvUk8+HRYhiJxQvLF261BZ558+f32nzHj16eNa90Njx0u/K6wcffNAel1WrVrU/K30q40mQJ4pwu8vnSeJg/wWLZs2aWQMGDLB27NhhjR492mWXuGXLFqt169ZWjhw5rDvuuMO5ab399tt228taJ2uePLAePXo00ePgq6++smrXrm2fP2fOnFb16tWtd955JxV/fWzCOZgMEhn5cTfddJOVMWNGq3PnztaIESPsxpFG0owZM8bWz8pEkB/9xhtv2J/dunWrlSFDBs/zy7mkk+TJYtKkScYnCJlw8pQxf/58695777XFcKtXr7aGDBlibdy4MSIbFJmcMpFvvfVW+7d8/fXX1v3332//tvDA+Ouvv+wNlajQpHNl0HzzzTf2YnDs2DGrT58+dj3ZrMj1zJ4929701KxZ05o+fbr1xBNPWLt377avSxDx4T333GPVq1fPvm5BBlo8IBvC6667zp4QIs7MnTu3vekV6V5yjJ0w8hQqfSn9JZNdNp4yTqVvO3ToYJ9HoKg9cbD/gsmdd95pPfXUU9aMGTOsnj172u+dO3fOXmcbN25sP2xlzZrVfl/6S2xu7rrrLuvhhx+2JfDDhg2zfvvtN2vBggV230UyDkTCIGtq8+bNrYEDB9rviURIzhFeM0ni4RwEQklk6dKlIloIzZw50359/vz5UPHixUN9+vRx1du2bZtdL1++fKEjR44470+aNMl+f8qUKc573bp1C2XLls0uz58/P5QzZ85QmzZtQmfOnHGds0mTJvZfmC+++CJ0ySWXhObNm+eqN3LkSPs7FixY4Ptb5FxSb/Dgwc57f//9d6hmzZqhggULhv755x/7vbffftuuN3r0aKeeHGvYsGEoe/bsoRMnTtjvTZw40a738ssvu76nY8eOoXTp0oU2b97svCe/V353LNC7d2/7d0fChAkT7LpLliwx1knM2HnuuecSfLe8lnGxdu1a1/sHDx60j8lnyP/D/gs+o0aNumC/5MqVK1SrVi27LGuP1O/fv7+rjqyl8v6XX37pev+HH35wvR/JOJB7gqzl586du8hfF/twDiaNSy5GGiOixmuuucZ+Lbu8Tp062SLEf//9N0F9OSZqqDBXXXWV/b/sCDUiyZAnBNnBy+5SxFl+iFREpDCVKlWyDh065PyJKDV8vgshukHZrYYRSYy8ll2vqJyEadOmWYULF7afLsLIblaeVkTt9fPPPzv1Lr30Uvt9RFRNMja+//57K96Rpwdh6tSptkGiH4kZO5omTZpYVapU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YOFGhhBCCCGBhRsZQgghhASWmN7IbN++3UqXLp315ptvXrDu888/b9clF0f37t2t7NmzX7Be06ZN7b/kQs5VrVq1ZDsfSV0+/fRTe/7JnE3KmCtdunSKXBeJDPZffNwno5U03chI40XyN2fOHCuaOH36tL3xibbrSirDhw+327l+/fppfSmB5NVXX7UmTpxoBY3Vq1dbHTt2tEqVKmVlzpzZKlasmNWiRQvr3XffTetLIxHA/gsO7KuUJb2VhnzxxReu159//rk1c+bMBO9Xrlw5xa/lmWeesfr37x/xRuaFF16wy8kpVUgrvvzyS/uJ6Ndff7U2b95slS9fPq0vKXAbGVmk2rdvbwWFhQsXWtdcc41VsmRJq2fPnlbhwoWtnTt3WosXL7beeecd66GHHkrrSyQ+sP+CA/sqxjcyXbp0cb2WjpWNjH4/NUifPr3958f58+etf/75x4oltm3bZk+08ePHW7169bI3Nc8991xaXxZJYV555RUrV65c1pIlS6zcuXO7jh04cCDNrotEBvsvOLCvLPvhP2vWrCl2/kDbyCxdutRq2bKllT9/fitLlixWmTJlrB49enjW/eCDD6xy5cpZmTJlsurWrWsPqgvZyMjrBx980L65V61a1f7syJEjrQIFCtjHRSoTVn/J54OI/LY8efJYbdq0saUK8tpPh3qhdvRixYoVdpuJ9OrPP/801vv777/tTZRIhOT8JUqUsPr162e/HynLli2zGjVq5IwH6S+NLB533323VahQIVvMe/nll1ufffZZgnqnTp2yHnvsMfs65Houu+wyuw1CoZBTR9pF6snnw2NBdP7RzpYtW+wxrRdWoWDBgk551KhRVrNmzez3pA2qVKlijRgxIsFnRKLXtm1ba/78+Va9evXsdi1btqwtZdWsXbvWPqf0UfHixa2XX37ZfkjQTJo0yR6XRYsWtb9bxt1LL71k/fvvv1a8w/6Lvb4K329ETS32ftJm8rkffvghwed2795t3+tkDcv0f/U++eQTVx156H722Wet2rVr2xupbNmyWVdddZU1e/bsC16zrHH33nuvlTFjRvshN8zo0aPt80nf582b17rtttts6ZKXvaKsxVdffbW9gXnqqaesmJXIXAxyM7ruuuvsG6SohGSQyA0XGz3MmDFjrJMnT9oSBxksb7zxhnXTTTdZW7dutTJkyOD7PbNm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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.386605Z",
     "start_time": "2025-02-05T00:57:18.383592Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 数据加载与预处理\n",
    "# 使用了PyTorch的`DataLoader`类来加载数据，它可以将数据集（Dataset）切分为更小的批次（batch），每次迭代提供一小批量数据，而不是单个数据点。\n",
    "\n",
    "# 作用：创建一个数据加载器，用于加载训练集。\n",
    "# 参数：\n",
    "# train_ds：训练集数据集对象,\n",
    "# batch_size：每次加载的样本数量,控制每次加载的样本数量，影响内存占用和训练速度。\n",
    "# 太小导致梯度震荡，太大占用内存。\n",
    "# shuffle=True：在每个 epoch 开始时打乱数据顺序，确保模型不会记住样本的顺序。\n",
    "# 原理：\n",
    "# DataLoader 将数据集分成多个批次（batch），每个批次包含 batch_size 个样本。\n",
    "# 在训练过程中，模型会逐批次地读取数据并进行训练。\n",
    "# shuffle=True 确保每个 epoch 中数据的顺序不同，避免模型过拟合。\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32, shuffle=True)\n",
    "\n",
    "# 作用：创建一个数据加载器，用于加载验证集或测试集。\n",
    "# 参数：\n",
    "# shuffle=False：不打乱数据顺序，确保验证或测试结果可重复。\n",
    "# 原理：\n",
    "# 在验证或测试过程中，模型会逐批次地读取数据并进行评估。\n",
    "# shuffle=False 确保数据顺序固定，便于结果的可重复性和分析。\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=32, shuffle=False)"
   ],
   "id": "9859f052cdeedf72",
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "在PyTorch中，`DataLoader`是一个**迭代器**，它封装了数据的加载和预处理过程，使得在训练机器学习模型时可以方便地批量加载数据。`DataLoader`主要负责以下几个方面：\n",
    "\n",
    "1. **批量加载数据**：`DataLoader`可以将数据集（Dataset）切分为更小的批次（batch），每次迭代提供一小批量数据，而不是单个数据点。这有助于模型学习数据中的统计依赖性，并且可以更高效地利用GPU等硬件的并行计算能力。\n",
    "\n",
    "2. **数据打乱**：默认情况下，`DataLoader`会在每个epoch（训练周期）开始时打乱数据的顺序。这有助于模型训练时**避免陷入局部最优解**，并且可以提高模型的泛化能力。\n",
    "\n",
    "3. **多线程数据加载**：`DataLoader`支持多线程（通过参数`num_workers`）来并行地加载数据，这可以显著减少训练过程中的等待时间，尤其是在处理大规模数据集时。\n",
    "\n",
    "4. **数据预处理**：`DataLoader`可以与`transforms`结合使用，对加载的数据进行预处理，如归一化、标准化、数据增强等操作。\n",
    "\n",
    "5. **内存管理**：`DataLoader`负责管理数据的内存使用，确保在训练过程中不会耗尽内存资源。\n",
    "\n",
    "6. **易用性**：`DataLoader`提供了一个简单的接口，可以很容易地集成到训练循环中。\n",
    "\n"
   ],
   "id": "e5b584e1b45f2826"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.402176Z",
     "start_time": "2025-02-05T00:57:18.388121Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#查看train_loader\n",
    "for datas, labels in train_loader:\n",
    "    print(datas.shape)\n",
    "    print(labels.shape)\n",
    "    break\n",
    "#查看val_loader\n",
    "for datas, labels in val_loader:\n",
    "    print(datas.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ],
   "id": "bd0d35ecf199cb85",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([32, 1, 28, 28])\n",
      "torch.Size([32])\n",
      "torch.Size([32, 1, 28, 28])\n",
      "torch.Size([32])\n"
     ]
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "a389c6ce24338b5a"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.410562Z",
     "start_time": "2025-02-05T00:57:18.403181Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为NeuralNetwork的类，继承自nn.Module\n",
    "class NeuralNetwork(nn.Module):\n",
    "\n",
    "    # 定义网络的结构\n",
    "    def __init__(self):\n",
    "        # 调用父类nn.Module的初始化方法，完成模型的初始化\n",
    "        super().__init__()\n",
    "\n",
    "        # 定义一个Flatten层，用于将输入的多维张量展平为一维张量\n",
    "        # 例如，如果输入是(batch_size, 28, 28)的张量，展平后会变成(batch_size, 784)\n",
    "        self.flatten = nn.Flatten()\n",
    "\n",
    "        # 定义一个Sequential容器，包含多个线性层和激活函数\n",
    "        # Sequential容器会按照顺序依次执行其中的层\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "\n",
    "            # nn.Linear 是一个全连接层（线性层），其公式为 y = xA^T + b，A 是权重矩阵，b 是偏置向量\n",
    "            # nn.Linear(in_features, out_features) 中的 in_features 是输入特征的维度，out_features 是输出特征的维度\n",
    "            # 第一个全连接层，输入维度为784，输出维度为300\n",
    "            nn.Linear(784, 300),\n",
    "            # ReLU激活函数，引入非线性，公式为ReLU(x) = max(0, x)\n",
    "            nn.ReLU(),\n",
    "            # 第二个全连接层，输入维度为300，输出维度为100\n",
    "            nn.Linear(300, 100),\n",
    "            # 再次使用ReLU激活函数\n",
    "            nn.ReLU(),\n",
    "            # 第三个全连接层，输入维度为100，输出维度为10\n",
    "            # 这里的输出维度为10，通常对应于10个类别的分类任务\n",
    "            nn.Linear(100, 10),\n",
    "        )\n",
    "\n",
    "    # 定义前向传播函数，定义数据的前向传播过程\n",
    "    def forward(self, x):\n",
    "        x = self.flatten(x)  # 首先将输入张量展平\n",
    "        logits = self.linear_relu_stack(x)  # 然后将展平后的张量通过线性层和激活函数\n",
    "        return logits  # 返回最终的输出logits（未经过softmax处理的原始分数）\n",
    "\n",
    "\n",
    "# 实例化NeuralNetwork类，创建一个神经网络模型对象\n",
    "model = NeuralNetwork()"
   ],
   "id": "508d7bfa30f1bba5",
   "outputs": [],
   "execution_count": 15
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.424091Z",
     "start_time": "2025-02-05T00:57:18.411566Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 打印模型结构\n",
    "print(\"模型结构：\\n\", model)\n",
    "\n",
    "# 打印模型参数\n",
    "for name, param in model.named_parameters():\n",
    "    print(\"参数名：\", name, param.shape)\n",
    "\n",
    "# 这种方法拿到模型的所有可学习参数,包括模型参数和优化器参数\n",
    "print(\"模型参数：\\n\", list(model.parameters()))\n",
    "\n",
    "# 这种方法用于保存模型参数，看能看见参数属于模型的哪一部分\n",
    "# model.state_dict()  "
   ],
   "id": "6ac928326a446b8",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "模型结构：\n",
      " NeuralNetwork(\n",
      "  (flatten): Flatten(start_dim=1, end_dim=-1)\n",
      "  (linear_relu_stack): Sequential(\n",
      "    (0): Linear(in_features=784, out_features=300, bias=True)\n",
      "    (1): ReLU()\n",
      "    (2): Linear(in_features=300, out_features=100, bias=True)\n",
      "    (3): ReLU()\n",
      "    (4): Linear(in_features=100, out_features=10, bias=True)\n",
      "  )\n",
      ")\n",
      "参数名： linear_relu_stack.0.weight torch.Size([300, 784])\n",
      "参数名： linear_relu_stack.0.bias torch.Size([300])\n",
      "参数名： linear_relu_stack.2.weight torch.Size([100, 300])\n",
      "参数名： linear_relu_stack.2.bias torch.Size([100])\n",
      "参数名： linear_relu_stack.4.weight torch.Size([10, 100])\n",
      "参数名： linear_relu_stack.4.bias torch.Size([10])\n",
      "模型参数：\n",
      " [Parameter containing:\n",
      "tensor([[-0.0151, -0.0163, -0.0286,  ...,  0.0235,  0.0204, -0.0168],\n",
      "        [-0.0083,  0.0012, -0.0081,  ...,  0.0187, -0.0150,  0.0020],\n",
      "        [ 0.0005,  0.0201, -0.0343,  ...,  0.0069, -0.0018, -0.0220],\n",
      "        ...,\n",
      "        [-0.0107, -0.0040,  0.0268,  ...,  0.0220, -0.0039,  0.0185],\n",
      "        [ 0.0207, -0.0282,  0.0138,  ...,  0.0225, -0.0223, -0.0061],\n",
      "        [ 0.0080,  0.0207, -0.0261,  ...,  0.0186, -0.0119, -0.0316]],\n",
      "       requires_grad=True), Parameter containing:\n",
      "tensor([-0.0198, -0.0304,  0.0320, -0.0286,  0.0181, -0.0039, -0.0254, -0.0126,\n",
      "        -0.0020, -0.0140,  0.0200,  0.0228,  0.0011, -0.0057, -0.0178,  0.0329,\n",
      "         0.0308,  0.0191, -0.0198,  0.0074,  0.0038,  0.0261,  0.0186,  0.0205,\n",
      "        -0.0091,  0.0329, -0.0051, -0.0010,  0.0206, -0.0040,  0.0074, -0.0108,\n",
      "         0.0168,  0.0083, -0.0157, -0.0001, -0.0095,  0.0209, -0.0153,  0.0244,\n",
      "        -0.0239,  0.0235, -0.0150, -0.0208,  0.0331,  0.0276, -0.0157,  0.0157,\n",
      "         0.0194,  0.0211, -0.0323,  0.0277, -0.0306, -0.0015,  0.0072, -0.0270,\n",
      "         0.0047, -0.0353, -0.0030, -0.0111,  0.0156,  0.0111, -0.0290,  0.0133,\n",
      "        -0.0152, -0.0266, -0.0356,  0.0300,  0.0223,  0.0175,  0.0254,  0.0033,\n",
      "        -0.0160,  0.0183, -0.0130,  0.0296,  0.0319, -0.0159,  0.0199,  0.0130,\n",
      "        -0.0018,  0.0045,  0.0092, -0.0111, -0.0078,  0.0218, -0.0082,  0.0217,\n",
      "         0.0313, -0.0328, -0.0011,  0.0006, -0.0339, -0.0012, -0.0287,  0.0332,\n",
      "        -0.0169,  0.0089,  0.0309, -0.0270,  0.0326, -0.0118, -0.0128,  0.0212,\n",
      "         0.0083,  0.0133,  0.0287, -0.0151,  0.0307,  0.0245,  0.0166,  0.0152,\n",
      "        -0.0230, -0.0347, -0.0195,  0.0019,  0.0350, -0.0252, -0.0004,  0.0240,\n",
      "        -0.0166,  0.0277, -0.0211, -0.0061, -0.0142, -0.0298,  0.0200,  0.0103,\n",
      "        -0.0033,  0.0293,  0.0018, -0.0351,  0.0013, -0.0280, -0.0184, -0.0061,\n",
      "         0.0302, -0.0258,  0.0308, -0.0055, -0.0076, -0.0220, -0.0063, -0.0157,\n",
      "        -0.0307,  0.0302,  0.0117,  0.0035, -0.0131,  0.0306,  0.0269,  0.0122,\n",
      "        -0.0299, -0.0030, -0.0225,  0.0012, -0.0199,  0.0319, -0.0018, -0.0129,\n",
      "        -0.0142, -0.0194,  0.0152,  0.0241,  0.0125, -0.0004,  0.0068, -0.0144,\n",
      "        -0.0126,  0.0297, -0.0103,  0.0029, -0.0156, -0.0008,  0.0345,  0.0103,\n",
      "         0.0057,  0.0129, -0.0172, -0.0033,  0.0321, -0.0147,  0.0117, -0.0132,\n",
      "         0.0196, -0.0298,  0.0067, -0.0045,  0.0013, -0.0325,  0.0326,  0.0315,\n",
      "         0.0048, -0.0079,  0.0212,  0.0309, -0.0319, -0.0105, -0.0138, -0.0214,\n",
      "         0.0273, -0.0336, -0.0311,  0.0233, -0.0249,  0.0198,  0.0308,  0.0331,\n",
      "         0.0198,  0.0119, -0.0191, -0.0192, -0.0348,  0.0089, -0.0137,  0.0252,\n",
      "        -0.0223,  0.0116, -0.0248, -0.0350, -0.0249,  0.0266,  0.0202, -0.0210,\n",
      "         0.0314, -0.0113, -0.0321, -0.0231,  0.0269, -0.0173, -0.0201,  0.0232,\n",
      "        -0.0066,  0.0145, -0.0133,  0.0267,  0.0135,  0.0018,  0.0236,  0.0067,\n",
      "         0.0292,  0.0155,  0.0279, -0.0263,  0.0095,  0.0256, -0.0318, -0.0135,\n",
      "        -0.0054,  0.0127,  0.0354, -0.0090,  0.0261,  0.0137, -0.0318,  0.0195,\n",
      "         0.0124,  0.0235, -0.0106, -0.0328,  0.0214, -0.0244,  0.0074, -0.0238,\n",
      "         0.0040,  0.0114,  0.0047,  0.0170, -0.0144,  0.0248, -0.0348, -0.0015,\n",
      "         0.0316,  0.0076,  0.0338,  0.0024, -0.0154, -0.0039, -0.0042, -0.0065,\n",
      "         0.0164, -0.0091, -0.0015, -0.0265, -0.0003, -0.0222,  0.0099,  0.0182,\n",
      "         0.0134, -0.0031, -0.0268,  0.0289, -0.0347, -0.0099,  0.0202, -0.0031,\n",
      "         0.0269,  0.0104, -0.0205,  0.0016], requires_grad=True), Parameter containing:\n",
      "tensor([[-0.0188,  0.0012,  0.0392,  ...,  0.0135,  0.0008, -0.0021],\n",
      "        [-0.0107, -0.0250,  0.0286,  ...,  0.0179,  0.0104, -0.0259],\n",
      "        [-0.0063, -0.0542, -0.0500,  ..., -0.0507,  0.0348,  0.0317],\n",
      "        ...,\n",
      "        [ 0.0137,  0.0544, -0.0293,  ..., -0.0096, -0.0168, -0.0277],\n",
      "        [ 0.0162,  0.0134, -0.0411,  ...,  0.0086, -0.0414,  0.0075],\n",
      "        [ 0.0023, -0.0098, -0.0154,  ...,  0.0289, -0.0499,  0.0534]],\n",
      "       requires_grad=True), Parameter containing:\n",
      "tensor([ 0.0039,  0.0389, -0.0160,  0.0088,  0.0572, -0.0455,  0.0041, -0.0084,\n",
      "         0.0498, -0.0576, -0.0400, -0.0052,  0.0341,  0.0200, -0.0185, -0.0296,\n",
      "         0.0322, -0.0077, -0.0008, -0.0143, -0.0047,  0.0111,  0.0057, -0.0383,\n",
      "        -0.0214, -0.0001,  0.0397,  0.0270,  0.0115, -0.0117, -0.0392,  0.0415,\n",
      "        -0.0444, -0.0079, -0.0504, -0.0373, -0.0258,  0.0337,  0.0460, -0.0477,\n",
      "         0.0203, -0.0030,  0.0267,  0.0081,  0.0447,  0.0453, -0.0496,  0.0438,\n",
      "         0.0219,  0.0048, -0.0519,  0.0292, -0.0136,  0.0141, -0.0067, -0.0461,\n",
      "         0.0178,  0.0120, -0.0010,  0.0084, -0.0109,  0.0263, -0.0131,  0.0380,\n",
      "         0.0146,  0.0510,  0.0023,  0.0097,  0.0172, -0.0040,  0.0113, -0.0086,\n",
      "         0.0128, -0.0172,  0.0023,  0.0088,  0.0189,  0.0251,  0.0497,  0.0438,\n",
      "         0.0513,  0.0397, -0.0550, -0.0228, -0.0270, -0.0310,  0.0158,  0.0218,\n",
      "         0.0449, -0.0210, -0.0177,  0.0422, -0.0073,  0.0458,  0.0325, -0.0190,\n",
      "        -0.0408,  0.0337, -0.0464, -0.0438], requires_grad=True), Parameter containing:\n",
      "tensor([[ 1.1925e-02, -4.1538e-02, -1.2034e-02, -8.3273e-02, -5.5963e-02,\n",
      "          4.2658e-03,  5.7476e-03,  7.0398e-02, -6.5037e-02,  4.1017e-02,\n",
      "         -7.4892e-02, -4.7933e-02,  4.1038e-02,  5.3672e-04, -4.9579e-02,\n",
      "          8.3382e-02,  2.2876e-03, -5.4403e-02,  4.5930e-02,  1.9978e-02,\n",
      "          3.5716e-02,  5.5213e-02,  5.2398e-03,  6.7131e-02,  8.5890e-02,\n",
      "          6.1590e-02,  4.0094e-02, -7.5661e-02, -8.8585e-03, -2.7008e-02,\n",
      "          8.0184e-02, -3.6251e-02, -5.4624e-02, -5.8163e-02, -9.2654e-03,\n",
      "          5.6129e-02, -8.8767e-02,  3.8980e-02, -3.0329e-03, -1.8921e-04,\n",
      "         -9.1201e-02,  7.1517e-02, -7.4163e-02, -1.1660e-02,  7.0904e-02,\n",
      "          9.7999e-02, -2.7479e-02, -8.1536e-02, -9.5394e-02,  2.1259e-02,\n",
      "         -9.1785e-03, -5.4035e-02, -8.2875e-02, -1.7234e-03, -8.0951e-02,\n",
      "         -7.6448e-02,  3.5256e-02,  7.5749e-02, -7.2559e-02, -8.2639e-02,\n",
      "          3.3086e-02, -6.1410e-02, -1.4095e-02,  2.3176e-02,  9.8641e-02,\n",
      "          9.8591e-02, -6.9133e-03, -3.6749e-02, -3.6397e-02, -9.2333e-02,\n",
      "         -3.5975e-02, -4.0703e-03, -1.3275e-02,  6.9766e-03,  5.7712e-02,\n",
      "         -8.3509e-02, -9.8595e-02,  9.8524e-02,  4.9178e-02,  2.7736e-02,\n",
      "         -3.5283e-02,  6.7588e-02, -6.8974e-02, -2.8194e-02, -3.1437e-02,\n",
      "         -1.1588e-02,  3.7271e-02, -9.1416e-02,  2.6063e-03, -9.5599e-03,\n",
      "         -9.9301e-02,  5.7406e-02,  6.1950e-02,  7.2129e-02, -7.5114e-02,\n",
      "          5.9650e-02, -4.8063e-02, -9.8334e-02,  7.8588e-02, -6.1995e-02],\n",
      "        [ 9.6213e-02, -3.5250e-02,  9.2944e-02,  6.5217e-02, -1.8622e-02,\n",
      "          9.7090e-02, -9.4110e-03,  5.3312e-02, -6.2142e-02, -4.0116e-02,\n",
      "         -5.7141e-02, -1.1723e-02,  4.4790e-02, -2.9040e-02, -9.3139e-02,\n",
      "         -4.2445e-02, -3.6491e-02, -3.0654e-02, -6.0456e-02,  1.3036e-02,\n",
      "          5.0489e-04, -2.8066e-02, -2.7320e-02, -5.0709e-02,  5.4982e-02,\n",
      "         -3.1188e-02, -6.5091e-02, -9.7147e-02,  9.9433e-02, -5.7331e-02,\n",
      "         -1.6618e-02, -8.3590e-03, -3.1599e-02, -5.2240e-02,  5.4072e-02,\n",
      "          6.2593e-02, -2.8015e-02, -1.8292e-02, -3.5521e-02,  7.5514e-02,\n",
      "         -5.1288e-02,  4.4433e-02,  9.8086e-02,  7.0745e-02,  7.4519e-02,\n",
      "          1.1525e-02, -8.3951e-02,  9.8144e-02, -3.9743e-02,  9.0806e-03,\n",
      "         -8.1981e-02, -1.9059e-02, -6.6918e-02,  1.0667e-02,  5.9338e-03,\n",
      "          2.4286e-02,  6.8834e-02,  4.8444e-02, -9.5856e-03, -3.3968e-02,\n",
      "          1.7074e-02, -3.1432e-02,  5.3738e-02,  4.1044e-02,  2.8926e-02,\n",
      "         -3.0892e-02,  2.1695e-02, -5.0693e-02,  1.7619e-02, -1.3200e-03,\n",
      "          2.7661e-02,  1.4165e-02, -4.7909e-02,  2.2071e-02, -5.6660e-02,\n",
      "         -1.0870e-03,  8.1646e-02, -2.4957e-02,  9.1810e-02, -8.0883e-02,\n",
      "          2.6681e-02, -7.3915e-02,  2.4298e-02,  3.0780e-02, -7.1604e-02,\n",
      "          6.2195e-03,  6.8062e-02, -4.0802e-02,  9.3010e-02, -3.5378e-02,\n",
      "          7.2626e-02,  9.0559e-02, -3.1075e-02,  1.1750e-02,  7.3866e-02,\n",
      "         -3.6689e-02, -3.3742e-02,  7.7392e-02, -9.6375e-02, -3.4740e-02],\n",
      "        [-1.0452e-02, -1.1021e-02, -5.7102e-02, -8.7776e-02, -7.9223e-02,\n",
      "          3.9563e-02, -8.3100e-02, -6.9747e-02, -6.4355e-03,  2.3657e-02,\n",
      "          1.7747e-02,  2.5178e-02,  8.2991e-02, -6.0889e-02,  2.3813e-02,\n",
      "          6.1075e-02, -6.2022e-02, -3.6664e-02, -9.8786e-02,  2.2445e-03,\n",
      "          1.2324e-03,  2.1886e-02, -6.8353e-03, -8.2589e-02,  8.5899e-02,\n",
      "         -5.6038e-02,  5.7486e-02,  3.8842e-02, -5.6587e-05, -8.9419e-02,\n",
      "          6.4844e-02,  1.3152e-02,  1.8683e-02,  6.4169e-02,  5.3186e-02,\n",
      "          2.6133e-02, -1.5742e-02, -8.3968e-02, -5.4276e-02,  1.9125e-03,\n",
      "         -2.7761e-02, -6.8297e-02, -6.2908e-02,  5.9207e-02, -2.9851e-02,\n",
      "          4.5700e-03,  5.1253e-02,  6.4350e-02, -7.4578e-03,  3.9353e-02,\n",
      "          1.0023e-02, -7.5288e-02,  6.4838e-02, -5.6971e-02, -9.0571e-02,\n",
      "         -5.9407e-02, -8.3661e-02,  9.6942e-02,  2.1207e-02,  4.9768e-02,\n",
      "          6.7485e-02,  6.9907e-02,  6.4181e-02,  6.0124e-02, -2.5040e-02,\n",
      "          6.2421e-02,  2.6460e-03, -2.2600e-02,  7.3320e-03, -9.4337e-02,\n",
      "         -2.0471e-02,  9.7736e-02,  2.2573e-02,  8.7203e-02,  2.9307e-02,\n",
      "         -8.0891e-02,  8.1744e-04,  8.0991e-02, -4.9126e-02, -5.3638e-02,\n",
      "          1.8879e-02,  7.9755e-02, -7.4089e-02, -4.6231e-03,  9.6038e-02,\n",
      "         -7.7485e-02,  5.0295e-02, -8.0239e-02,  1.5937e-02, -4.8141e-02,\n",
      "         -9.3799e-02,  3.1105e-02,  5.0787e-02, -1.1495e-02, -8.4584e-03,\n",
      "         -8.3858e-03,  7.3139e-02,  3.6699e-02,  4.9095e-02,  5.9140e-02],\n",
      "        [ 4.2765e-02,  3.4465e-02,  4.1122e-02, -3.3874e-02, -4.2765e-02,\n",
      "          1.2304e-02,  6.4187e-02,  7.8165e-03,  4.6933e-02,  5.2562e-02,\n",
      "          2.5496e-02, -5.4687e-02,  9.3915e-02, -2.7970e-02, -5.8637e-02,\n",
      "          2.5573e-02,  9.5855e-03,  7.1435e-02, -9.1553e-02,  3.0405e-02,\n",
      "          7.4374e-02, -6.8166e-02,  6.5097e-02,  8.1656e-02,  2.2359e-02,\n",
      "          6.4328e-02, -9.9778e-02,  7.7141e-02, -6.4893e-02, -4.5359e-02,\n",
      "          5.8544e-02, -2.8496e-03,  6.0505e-02,  5.0979e-02,  4.9006e-02,\n",
      "          9.3927e-02,  1.8239e-02, -9.1894e-02,  7.0900e-02,  8.9276e-02,\n",
      "          2.2757e-02, -3.7834e-02,  3.8647e-03, -6.1614e-02,  2.5875e-02,\n",
      "         -4.0252e-02, -5.3413e-02, -7.8789e-02, -1.4081e-02,  2.3901e-02,\n",
      "          8.0149e-02,  7.8860e-03,  2.3618e-03, -6.4951e-02,  7.2331e-02,\n",
      "          9.1446e-02,  5.0759e-02, -9.5482e-02, -7.4623e-02, -3.4160e-02,\n",
      "         -7.4502e-02,  3.2311e-02,  9.9993e-02, -5.9796e-03,  8.0815e-02,\n",
      "          7.8698e-02,  7.8809e-02,  7.3501e-02,  3.7267e-02, -4.3377e-02,\n",
      "          3.7496e-03,  9.0935e-02,  4.1110e-02, -6.1544e-02,  3.2407e-02,\n",
      "         -2.7437e-02,  5.2226e-02, -8.1793e-03, -6.3407e-02,  8.0226e-03,\n",
      "          2.1256e-02,  2.9128e-02,  1.7656e-02, -4.5351e-02,  4.9662e-03,\n",
      "          9.6477e-02,  8.4443e-02, -1.4819e-02,  8.2024e-02, -5.4749e-02,\n",
      "         -7.8488e-02,  6.6582e-02,  8.9653e-02,  7.3219e-03, -4.8173e-02,\n",
      "          4.5357e-02,  1.8449e-03,  6.1185e-02,  3.8238e-02, -5.2717e-03],\n",
      "        [ 5.1909e-02, -9.4238e-02, -7.5381e-02, -9.0138e-02, -3.9340e-02,\n",
      "          1.0059e-02,  1.2284e-02, -1.2007e-02,  2.6194e-02,  7.4650e-02,\n",
      "         -2.9790e-02, -5.5203e-02,  7.0511e-03,  1.2672e-02,  4.4736e-02,\n",
      "          6.5091e-02,  3.3451e-02,  5.2686e-02, -5.7813e-02, -6.7679e-03,\n",
      "         -4.9153e-03,  3.8399e-03,  5.3386e-02,  3.8479e-03,  4.8354e-02,\n",
      "          8.8934e-02,  5.4989e-02, -2.3832e-02,  5.8203e-02,  6.4723e-02,\n",
      "          7.9901e-02,  9.6293e-02, -1.2808e-02, -7.9477e-02,  4.8667e-02,\n",
      "          2.1295e-02, -8.1699e-02,  1.9785e-02, -8.4781e-02,  1.3889e-02,\n",
      "          5.5692e-02,  2.8568e-02, -7.6148e-02, -6.9905e-02,  9.9305e-02,\n",
      "          1.7719e-02,  7.8680e-02, -7.7847e-02, -5.8065e-02,  7.2672e-02,\n",
      "          3.6886e-02, -5.8980e-02,  9.5322e-02,  8.7609e-02,  1.3429e-02,\n",
      "          2.1667e-02, -3.9447e-02,  7.8324e-02,  4.8724e-02, -2.4149e-02,\n",
      "          8.5947e-02, -7.5900e-02, -7.8222e-02, -7.6098e-02,  7.3776e-02,\n",
      "          8.9158e-03, -8.9124e-02, -2.8761e-02,  7.2992e-02,  7.6422e-02,\n",
      "          5.3928e-03, -6.3057e-02,  1.8223e-03,  7.8732e-02, -6.6911e-02,\n",
      "          8.1002e-02, -3.8649e-03,  6.6216e-02, -8.3920e-02,  2.8129e-02,\n",
      "         -5.9406e-02, -9.3018e-02, -3.4414e-02, -4.1184e-03,  8.2134e-02,\n",
      "         -5.8835e-02,  7.2828e-02, -6.8597e-02,  9.4989e-02,  5.9311e-02,\n",
      "          9.9486e-02, -5.9291e-02,  9.0026e-02, -7.7741e-02, -6.4514e-02,\n",
      "          3.9030e-02, -4.7903e-02, -9.0313e-02,  1.1450e-02, -8.9782e-02],\n",
      "        [ 1.3871e-02, -6.6067e-02, -8.7458e-02, -9.1906e-02, -8.8812e-02,\n",
      "          3.6124e-03, -3.8462e-02, -2.1079e-02,  7.2588e-02, -6.0358e-02,\n",
      "          9.2256e-02,  5.9818e-02,  9.5680e-02,  7.4701e-02, -3.8181e-03,\n",
      "          7.2148e-02,  6.7821e-02,  8.2229e-02, -4.0608e-02, -3.9699e-02,\n",
      "          8.3877e-02, -4.3003e-02,  3.8393e-02,  4.1395e-02,  5.4460e-02,\n",
      "          8.9836e-02, -9.8932e-02, -8.6573e-02, -3.4168e-03, -1.3999e-02,\n",
      "         -5.6605e-02, -1.6512e-02,  8.1935e-02,  5.4056e-02, -8.1684e-02,\n",
      "          6.0779e-02,  2.4469e-02,  7.5377e-02,  8.0813e-02, -4.8607e-02,\n",
      "          9.0524e-02, -1.6322e-02,  9.5043e-02,  5.6623e-02, -7.6446e-02,\n",
      "         -1.5001e-02,  5.8266e-02, -2.4817e-02,  1.5726e-02, -2.1218e-03,\n",
      "          8.7213e-02, -8.5443e-02, -6.6116e-02,  5.6071e-02, -3.6742e-02,\n",
      "          5.0631e-02, -3.2151e-02,  8.4294e-02, -1.7887e-02, -6.4828e-02,\n",
      "          1.4592e-02, -8.0755e-02, -7.9350e-02, -7.5987e-02, -5.1028e-02,\n",
      "          3.0300e-02, -9.6569e-02, -4.1745e-02, -9.6528e-02, -6.8222e-02,\n",
      "          7.6027e-02,  8.1256e-03, -2.3561e-03,  1.1320e-02,  4.4444e-02,\n",
      "         -1.3835e-02, -3.0933e-02,  4.6563e-02,  1.9682e-02,  3.2922e-02,\n",
      "          2.6638e-02, -6.6525e-02,  8.7380e-03,  4.1105e-02, -1.4899e-02,\n",
      "         -7.5056e-02, -1.2798e-02, -7.4426e-02, -4.5214e-02,  5.2563e-02,\n",
      "         -3.9399e-02, -3.6685e-02, -2.3609e-02, -6.6580e-02,  7.3275e-02,\n",
      "          2.4293e-02, -2.2506e-02,  2.0991e-02, -6.3922e-02,  3.7499e-03],\n",
      "        [ 1.9970e-02,  3.1856e-02,  2.5459e-02,  8.3474e-02, -1.8197e-02,\n",
      "          3.7137e-03,  1.9046e-02,  2.3573e-02, -8.7685e-02, -8.0170e-02,\n",
      "         -7.8491e-02, -4.0657e-02, -4.8823e-03,  7.3660e-02, -8.5773e-02,\n",
      "          5.2181e-02, -1.3849e-02, -8.9722e-02, -5.7304e-02,  9.0679e-02,\n",
      "          6.7963e-03,  1.2895e-02,  3.2530e-04,  6.6897e-02, -2.1166e-03,\n",
      "         -3.3779e-02, -5.6875e-02, -1.9888e-03,  5.2288e-02,  2.2289e-02,\n",
      "          3.3733e-04, -5.7796e-04, -5.3005e-02,  3.8319e-02,  7.2785e-02,\n",
      "          1.9615e-02,  9.7840e-02, -9.6351e-02, -7.3788e-02, -8.8113e-02,\n",
      "          4.1007e-02,  1.8945e-02,  1.8795e-02,  4.4122e-02, -7.3600e-02,\n",
      "          5.7750e-02, -1.3524e-02, -5.5313e-02,  6.9464e-02,  3.5209e-03,\n",
      "          2.2901e-02, -8.5481e-02,  7.1457e-02, -9.4086e-02,  8.9188e-02,\n",
      "         -2.1942e-02,  3.9127e-02,  7.2555e-02,  7.7316e-02,  6.7238e-02,\n",
      "         -1.1237e-03,  5.7810e-02,  7.9700e-02, -7.6357e-02, -6.3271e-02,\n",
      "          7.4179e-03, -8.1212e-02,  1.2602e-02, -2.5639e-02, -5.4469e-02,\n",
      "          2.8496e-02, -1.0519e-02, -3.9659e-04, -8.5200e-02,  2.6699e-02,\n",
      "          8.4998e-02,  5.6002e-02, -9.9026e-02, -4.5693e-02,  4.9732e-02,\n",
      "         -7.2976e-02,  3.6799e-02, -4.6862e-02,  8.1726e-03,  8.0724e-02,\n",
      "          7.8833e-02,  2.3577e-02,  5.5823e-02, -6.0122e-02, -1.9306e-02,\n",
      "          2.3929e-02,  2.7672e-02,  2.6900e-02,  5.2224e-02,  5.7601e-02,\n",
      "         -2.0184e-02,  8.4722e-02,  7.9315e-02,  4.9047e-02,  3.5791e-02],\n",
      "        [-5.6302e-03, -4.7682e-02,  4.7481e-02, -7.2824e-02,  5.3079e-02,\n",
      "         -9.5688e-02,  2.1491e-02,  6.8583e-03, -4.7153e-02,  2.1320e-02,\n",
      "          3.8040e-02, -4.0472e-02, -4.4600e-02,  3.3790e-03, -9.3061e-02,\n",
      "          6.6863e-02,  3.3040e-02,  1.8608e-02, -8.3644e-02, -2.4868e-02,\n",
      "         -7.1365e-02,  9.7798e-02, -9.4118e-03, -8.9319e-02, -2.2845e-02,\n",
      "         -8.6132e-02, -7.7748e-02, -2.6460e-02,  9.7128e-02,  9.6980e-02,\n",
      "         -1.3098e-02,  7.9677e-02,  1.2742e-02,  3.3047e-02,  5.1357e-02,\n",
      "          2.7428e-02,  8.1910e-02,  8.1996e-02,  1.6413e-03,  8.9728e-02,\n",
      "          5.0414e-02,  4.3768e-02,  7.9900e-02,  2.5424e-02,  3.9932e-02,\n",
      "          7.1091e-02,  2.2973e-02, -7.1836e-02,  1.0404e-02,  7.9378e-02,\n",
      "          8.6709e-03,  4.2557e-02, -2.2303e-02,  4.2703e-03, -3.7785e-02,\n",
      "         -9.9177e-02, -5.4442e-02, -5.4503e-02,  5.1267e-02,  6.8504e-02,\n",
      "          8.8930e-02,  9.7824e-02,  1.2758e-02, -5.0981e-02, -9.7870e-02,\n",
      "          1.5571e-02, -7.4732e-02,  6.9760e-02,  4.3699e-02, -1.6058e-02,\n",
      "         -3.2933e-02, -3.8698e-02,  1.6188e-02, -7.0829e-02,  5.5993e-02,\n",
      "         -2.5137e-02, -3.3444e-02,  7.0256e-02, -8.8904e-02,  3.4648e-02,\n",
      "          5.9276e-02, -2.3770e-02, -9.0118e-02, -8.3856e-02, -2.0868e-02,\n",
      "          8.9830e-02,  3.8891e-02,  5.4138e-02, -5.1403e-02,  6.5175e-02,\n",
      "          3.9889e-02,  1.9944e-02,  5.2092e-02,  4.6030e-02,  1.3893e-02,\n",
      "         -5.6241e-02, -8.3438e-02,  4.7530e-02,  8.2169e-02,  8.4302e-02],\n",
      "        [-5.2795e-02, -3.2667e-02,  9.1222e-02,  5.9680e-02,  1.3907e-02,\n",
      "          2.1975e-02, -1.7079e-02,  5.5459e-02,  9.3827e-02, -8.7049e-02,\n",
      "         -3.9204e-03,  6.5380e-02,  7.8498e-02, -7.9296e-02,  4.0517e-02,\n",
      "         -1.1922e-02,  7.1596e-02,  5.4722e-02, -2.7230e-02, -3.5008e-02,\n",
      "         -8.4418e-02, -9.9288e-02,  1.8234e-02,  3.5601e-02, -5.1170e-02,\n",
      "         -1.4642e-02,  2.4146e-02, -1.5318e-02,  4.2177e-02,  5.9831e-02,\n",
      "          4.6285e-02, -9.2913e-02, -2.8911e-02,  3.7933e-02,  5.2731e-02,\n",
      "          7.7128e-02, -1.6408e-02,  8.0047e-02, -2.3546e-03, -9.5550e-02,\n",
      "         -8.1601e-03, -7.5092e-02, -2.1469e-02, -4.1012e-02,  1.5488e-02,\n",
      "          1.7331e-02,  8.3781e-02,  3.5393e-02,  8.1153e-02, -5.8951e-02,\n",
      "          9.1957e-02,  2.8978e-02, -7.2778e-02, -8.7601e-02, -2.2887e-02,\n",
      "         -9.6254e-02,  2.4161e-02, -2.8844e-02, -8.6523e-02, -9.7356e-03,\n",
      "          6.7213e-02,  2.3160e-02,  4.7416e-02, -8.1836e-02,  5.0812e-02,\n",
      "         -7.2307e-02,  6.3702e-02,  5.2068e-02,  7.5127e-02,  8.7768e-02,\n",
      "          8.5914e-02, -4.9235e-02, -6.6907e-02,  9.9426e-02,  2.1533e-02,\n",
      "          5.5104e-02,  8.6628e-02,  6.0465e-02,  1.0279e-02,  2.0034e-02,\n",
      "         -9.5557e-02, -3.7236e-02,  8.6741e-02,  1.0586e-02,  4.6530e-02,\n",
      "         -8.4687e-02, -2.8456e-03,  8.2019e-02, -2.9205e-02, -2.1171e-02,\n",
      "         -2.6122e-02,  2.6210e-02,  9.8415e-02,  8.6408e-02, -9.8746e-02,\n",
      "          2.0452e-02, -4.9866e-02, -9.8814e-02, -4.7009e-02,  4.6059e-02],\n",
      "        [ 7.6153e-03,  2.2748e-02,  2.0439e-02, -4.7900e-03,  5.4677e-02,\n",
      "          9.6610e-02, -7.0033e-02, -8.7287e-02, -5.2990e-02, -8.6048e-02,\n",
      "          1.9111e-02,  2.3866e-03,  1.7898e-02, -7.7955e-02, -8.7119e-02,\n",
      "          1.9448e-02,  6.4557e-02,  3.1966e-02, -9.3656e-02, -7.4614e-03,\n",
      "          5.4868e-02, -2.5763e-02, -6.6294e-03,  9.2176e-02,  4.1457e-02,\n",
      "         -4.0144e-02, -8.5474e-02,  2.8720e-02, -5.5057e-03,  8.6734e-02,\n",
      "         -8.9038e-03, -3.0552e-02,  9.9529e-02, -5.8188e-02,  3.0993e-02,\n",
      "         -7.4872e-02,  7.1450e-02,  2.7503e-02, -1.6251e-02,  2.3195e-02,\n",
      "          8.1735e-02, -2.8844e-02,  1.1761e-02, -9.7800e-02,  6.9317e-02,\n",
      "         -1.9195e-02, -3.6195e-02, -1.0970e-02,  9.5808e-02,  2.7177e-02,\n",
      "         -1.1457e-02, -3.6418e-02, -2.5239e-03,  5.4464e-02,  3.1267e-02,\n",
      "         -4.7546e-02,  6.2145e-02,  8.7008e-02,  1.0950e-02,  5.9275e-02,\n",
      "          9.0989e-03,  8.9620e-02,  1.6852e-02,  6.7569e-03, -2.5543e-02,\n",
      "          8.4954e-02, -9.0687e-03,  6.9752e-02,  5.1980e-02,  8.1715e-02,\n",
      "          8.4377e-02, -3.1424e-02, -4.3529e-02,  2.7450e-03,  8.4567e-02,\n",
      "         -6.4979e-03,  8.9386e-02, -3.1052e-02,  3.1361e-02,  7.2694e-02,\n",
      "          4.8026e-02,  5.4459e-03, -4.8260e-02, -7.0634e-02,  5.4878e-02,\n",
      "          7.3330e-02, -9.6803e-02,  4.5780e-03,  4.1069e-02, -9.3187e-02,\n",
      "          1.2696e-03, -3.0167e-03, -2.8186e-02, -2.3641e-03, -6.4223e-02,\n",
      "         -1.3204e-02, -7.5224e-02,  4.1656e-02,  6.0404e-02,  5.3468e-02]],\n",
      "       requires_grad=True), Parameter containing:\n",
      "tensor([-0.0196, -0.0193, -0.0766,  0.0200,  0.0234,  0.0624,  0.0176, -0.0016,\n",
      "        -0.0571, -0.0178], requires_grad=True)]\n"
     ]
    }
   ],
   "execution_count": 16
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.440130Z",
     "start_time": "2025-02-05T00:57:18.424594Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 查看模型运算的tensor尺寸\n",
    "x = torch.randn(32, 1, 28, 28)\n",
    "print(\"输入的x的尺寸：\\n\", x.shape)\n",
    "logits = model(x)\n",
    "print(\"输出的logits的尺寸：\\n\", logits.shape)"
   ],
   "id": "b1bb4324d02e8169",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "输入的x的尺寸：\n",
      " torch.Size([32, 1, 28, 28])\n",
      "输出的logits的尺寸：\n",
      " torch.Size([32, 10])\n"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ],
   "id": "674099f5103e94a4"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.444711Z",
     "start_time": "2025-02-05T00:57:18.440635Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义了一个损失函数使用的是交叉熵损失函数，这是分类问题中常用的损失函数。\n",
    "# CrossEntropyLoss 结合了 Softmax 激活函数和 Log Loss，内部先做softmax，再计算交叉熵，用于多类别分类问题。\n",
    "# 输入是未经过 Softmax 处理的 logits（原始预测分数），而目标标签应该是类别的索引\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 优化器：用于更新模型的参数。\n",
    "# 这里使用的是 SGD（随机梯度下降，Stochastic Gradient Descent）优化算法。通过对每个数据点的梯度进行更新，快速地调整模型参数。\n",
    "# model.parameters()：这一部分是传入模型的所有参数，优化器将会基于这些参数计算梯度并进行更新。\n",
    "# lr：学习率，控制参数更新的步长。学习率越小，更新越慢；学习率越大，可能会导致训练不稳定。0.001 是一个比较常见的初始学习率。\n",
    "# momentum：动量是为了加速 SGD，避免在训练过程中振荡。动量值通常在 0 到 1 之间，0.9 是一个常用的选择。动量帮助优化器在上一次梯度的方向上继续前进，从而使得优化过程更为稳定。\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)"
   ],
   "id": "c0144ba67e1581bd",
   "outputs": [],
   "execution_count": 18
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T00:57:18.511934Z",
     "start_time": "2025-02-05T00:57:18.444711Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "# 这是一个装饰器，用于禁用梯度计算。\n",
    "# 在评估模型时，我们不需要计算梯度，因为不会进行反向传播和参数更新。禁用梯度计算可以减少内存消耗并加速计算。\n",
    "@torch.no_grad()\n",
    "# 定义评估函数，接受三个参数：model（要评估的模型）、dataloader（数据加载器，用于提供评估数据）、loss_fct（损失函数）。\n",
    "# 用于评估模型在给定数据集上的性能。\n",
    "# 通过封装评估过程，可以方便地在不同模型和数据集上重复使用。\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    # 初始化三个空列表，分别用于存储每个批次的损失、预测标签和真实标签。\n",
    "    # 用于累积所有批次的损失和标签，以便后续计算平均损失和准确率。\n",
    "    # 累积所有批次的结果可以更准确地反映模型在整个数据集上的性能。\n",
    "    loss_list = []  # 记录损失\n",
    "    pred_list = []  # 记录预测\n",
    "    label_list = []  # 记录标签\n",
    "\n",
    "    # 遍历数据加载器中的每个批次，datas 是输入数据，labels 是对应的真实标签\n",
    "    for datas, labels in dataloader:\n",
    "        # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 将输入数据传递给模型，得到模型的输出 logits（通常是未归一化的预测分数）。\n",
    "        # 模型的前向传播过程，计算输入数据对应的输出。通过模型的输出可以计算损失和预测标签。\n",
    "        logits = model(datas)\n",
    "\n",
    "        # 将当前批次的损失值（转换为 Python 浮点数）添加到 loss_list 中。\n",
    "        # loss.item() 将张量中的单个值提取为 Python 浮点数。\n",
    "        # 累积所有批次的损失值，以便后续计算平均损失。\n",
    "        loss = loss_fct(logits, labels)\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 通过 argmax 函数获取模型预测的类别标签。axis=-1 表示在最后一个维度上取最大值对应的索引。\n",
    "        # logits 通常是每个类别的分数，argmax 找到分数最高的类别作为预测结果。\n",
    "        # 将模型的输出转换为具体的类别标签，便于与真实标签进行比较。\n",
    "        preds = logits.argmax(axis=-1)\n",
    "\n",
    "        # 将预测标签从 GPU 移动到 CPU，并转换为 NumPy 数组，再转换为 Python 列表，然后添加到 pred_list 中。\n",
    "        # preds.cpu().numpy().tolist() 将张量转换为 Python 列表。\n",
    "        # 累积所有批次的预测标签，以便后续计算准确率。\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    #计算预测标签和真实标签之间的准确率\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "\n",
    "    #返回所有批次的平均损失和准确率。\n",
    "    return np.mean(loss_list), acc\n"
   ],
   "id": "bacedb5978de59d5",
   "outputs": [],
   "execution_count": 19
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T01:11:19.745253Z",
     "start_time": "2025-02-05T00:57:18.512978Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 定义一个名为 training 的函数，用于训练模型。参数包括：\n",
    "# model: 要训练的模型。\n",
    "# train_loader: 训练数据的数据加载器。\n",
    "# val_loader: 验证数据的数据加载器。\n",
    "# epoch: 训练的轮数。\n",
    "# loss_fct: 损失函数。\n",
    "# optimizer: 优化器。\n",
    "# eval_step: 每隔多少步评估一次验证集性能，默认值为 500。\n",
    "# 该函数通过遍历训练数据，更新模型参数，并定期评估验证集性能。封装训练过程，方便重复使用\n",
    "def training(model, train_loader, val_loader, epoch, loss_fct, optimizer, eval_step=500):\n",
    "    # 初始化一个字典 record_dict，用于记录训练和验证过程中的损失和准确率。\n",
    "    # 通过记录训练和验证的性能，可以分析模型的训练过程。方便后续绘制训练曲线或分析模型表现。\n",
    "    record_dict = {\n",
    "        \"train\": [],  # \"train\": 存储训练集的损失和准确率。\n",
    "        \"val\": []  # \"val\": 存储验证集的损失和准确率。\n",
    "    }\n",
    "\n",
    "    # 初始化一个全局步数计数器 global_step，用于记录当前训练的步数。\n",
    "    # 步数用于控制何时评估验证集性能。通过步数而不是轮数来控制评估频率，更灵活。\n",
    "    global_step = 0\n",
    "\n",
    "    # 将模型设置为训练模式。在训练模式下，模型会启用一些特定于训练的功能（如 Dropout 和 BatchNorm）。\n",
    "    # 确保模型在训练时行为正确。\n",
    "    model.train()\n",
    "\n",
    "    # 使用 tqdm 创建一个进度条，总长度为 epoch * len(train_loader)，即总训练步数。\n",
    "    # tqdm 是一个进度条库，用于显示训练进度。\n",
    "    # 提供可视化的训练进度，方便监控训练过程。\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "\n",
    "        # 外层循环，遍历每个训练轮次。每个轮次会遍历整个训练数据集一次。\n",
    "        # 多轮训练可以提高模型的性能。\n",
    "        for epoch_id in range(epoch):\n",
    "\n",
    "            # 内层循环，遍历训练数据加载器中的每个批次。\n",
    "            for datas, labels in train_loader:\n",
    "\n",
    "                # 将输入数据和标签移动到指定的设备（GPU或CPU），转到GPU可以加速计算\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 清除优化器中之前的梯度。\n",
    "                # PyTorch 会累积梯度，因此在每次反向传播前需要清除之前的梯度。避免梯度累积导致错误的参数更新。\n",
    "                optimizer.zero_grad()\n",
    "\n",
    "                logits = model(datas)  # 模型前向计算\n",
    "                loss = loss_fct(logits, labels)  # 计算损失\n",
    "\n",
    "                # 计算损失相对于模型参数的梯度。\n",
    "                # 反向传播算法，通过链式法则计算梯度。梯度用于更新模型参数，以最小化损失。\n",
    "                loss.backward()\n",
    "\n",
    "                # 使用优化器更新模型参数。\n",
    "                optimizer.step()\n",
    "\n",
    "                preds = logits.argmax(axis=-1)  # 通过 argmax 函数获取模型预测的类别标签。\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())  # 计算当前批次的准确率\n",
    "                loss = loss.cpu().item()  # 将损失值从 GPU 移动到 CPU，并转换为 Python 浮点数。方便记录和打印损失值。\n",
    "\n",
    "                # 将当前批次的损失、准确率和步数记录到 record_dict[\"train\"] 中\n",
    "                # 累积训练过程中的性能指标。方便后续分析训练过程\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 每隔 eval_step 步评估一次验证集性能。\n",
    "                # 使用 global_step 控制评估频率。定期评估验证集性能，避免过拟合。\n",
    "                if global_step % eval_step == 0:\n",
    "                    # 将模型设置为评估模式\n",
    "                    model.eval()\n",
    "\n",
    "                    # 调用 evaluating 函数计算验证集的损失和准确率\n",
    "                    # 在验证集上评估模型性能。验证集性能反映了模型的泛化能力。\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "\n",
    "                    # 将验证集的损失、准确率和步数记录到 record_dict[\"val\"] 中。\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "\n",
    "                    # 将模型设置为训练模式\n",
    "                    model.train()\n",
    "\n",
    "                # 增加全局步数计数器\n",
    "                # 记录当前训练的步数,控制评估频率和记录训练进度\n",
    "                global_step += 1\n",
    "\n",
    "                # 更新进度条\n",
    "                pbar.update(1)\n",
    "\n",
    "                # 在进度条中显示当前训练的轮次。\n",
    "                # 更新进度条的后缀信息。方便监控训练进度。\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    # 返回记录的训练和验证性能的字典,包含训练和验证过程中的损失和准确率\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20  #改为40\n",
    "model = model.to(device)\n",
    "record = training(model, train_loader, val_loader, epoch, loss_fct, optimizer, eval_step=1000)"
   ],
   "id": "377f140667768127",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/37500 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "212e2d61726147318d14af9afb34c2ab"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-05T01:11:19.748802Z",
     "start_time": "2025-02-05T01:11:19.745253Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# record[\"train\"][-5:]是训练集的最后5个记录\n",
    "print(\"训练集的最后5个记录：\\n\", record[\"train\"][-5:])\n",
    "\n",
    "# record[\"val\"][-5:]是验证集的最后5个记录\n",
    "print(\"验证集的最后5个记录：\\n\", record[\"val\"][-5:])"
   ],
   "id": "a6bd5fb6c2039807",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集的最后5个记录：\n",
      " [{'loss': 0.19110490381717682, 'acc': 0.96875, 'step': 37495}, {'loss': 0.30758240818977356, 'acc': 0.8125, 'step': 37496}, {'loss': 0.23069694638252258, 'acc': 0.90625, 'step': 37497}, {'loss': 0.23233731091022491, 'acc': 0.90625, 'step': 37498}, {'loss': 0.10054080188274384, 'acc': 1.0, 'step': 37499}]\n",
      "验证集的最后5个记录：\n",
      " [{'loss': 0.29206398378809295, 'acc': 0.89555, 'step': 33000}, {'loss': 0.3042042903224627, 'acc': 0.8899333333333334, 'step': 34000}, {'loss': 0.2967854217986266, 'acc': 0.8930666666666667, 'step': 35000}, {'loss': 0.28361345337828, 'acc': 0.8991333333333333, 'step': 36000}, {'loss': 0.28403190287748975, 'acc': 0.8979, 'step': 37000}]\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "e232973db6437efa"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
